package collection;

import java.util.ArrayList;
import java.util.List;

/**
 * 泛型
 * 是jdk1.5时推出的一个特性
 *
 * 泛型，即“参数化类型”
 * 将类型由原来的具体的类型参数化，类似于方法中的变量参数，
 * 此时类型也定义成参数形式（可以称之为类型形参），然后在使用/调用时传入具体的类型（类型实参）
 * 泛型的本质是为了参数化类型（在不创建新的类型的情况下，通过泛型指定的不同类型来控制形参具体限制的类型）
 *
 * 泛型以用在类、接口和方法中，分别被称为泛型类、泛型接口、泛型方法
 *
 * 在编译之后程序会采取去泛型化的措施
 * 也就是说Java中的泛型，只在编译阶段有效
 * 在编译过程中，正确检验泛型结果后，会将泛型的相关信息擦出，并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法
 * 也就是说，泛型信息不会进入到运行时阶段
 *
 * 对此总结成一句话：
 *      泛型类型在逻辑上看以看成是多个不同的类型，实际上都是相同的基本类型
 *
 *
 * 泛型类
 * 泛型类型用于类的定义中，被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口
 *      class 类名称 <泛型标识（可以随便写任意标识号，标识指定的泛型的类型）> {
 *          private 泛型标识 /*（成员变量类型）* / var;
 *              .....
 *          }
 *      }
 *
 * 定义的泛型类，就一定要传入泛型类型实参吗？
 * 并不是这样，在使用泛型的时候如果传入泛型实参，则会根据传入的泛型实参做相应的限制，此时泛型才会起到本应起到的限制作用。
 * 如果不传入泛型类型实参的话，在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型（Object）
 *
 *
 * 泛型接口
 * 泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中
 *
 * //定义一个泛型接口
 * public interface Generator<T> {
 *     public T next();
 * }
 *
 *
 * 泛型方法
 * 泛型方法，是在调用方法的时候指明泛型的具体类型
 *
 * 泛型方法的基本介绍
 * * @param tClass 传入的泛型实参
 * * @return T 返回值为T类型
 * 说明：
 *      1）public 与 返回值中间<T>非常重要，可以理解为声明此方法为泛型方法。
 *      2）只有声明了<T>的方法才是泛型方法，泛型类中使用了泛型的成员方法并不是泛型方法。
 *      3）<T>表明该方法将使用泛型类型T，此时才可以在方法中使用泛型类型T。
 *      4）与泛型类的定义一样，此处T可以随便写为任意标识，常见的如T、E、K、V等形式的参数常用于表示泛型。
 *
 * public<T> T genericMethod(Class<T> tClass) throws InstantiationException, IllegalAccessException{
 *      T instance = tClass.newInstance();
 *      return instance;
 * }
 *
 *
 *
 * 泛型通配符
 * 类型通配符一般是使用“?”代替具体的类型实参，此处“?”是类型实参，而不是类型形参
 * 当操作类型时，不需要使用类型的具体功能时，只使用Object类中的功能，那么可以用 ? 通配符来表未知类型
 *
 * 同一种泛型可以对应多个版本（因为参数类型是不确定的），不同版本的泛型类实例是不兼容的
 *
 * 通配符不能用在泛型方法、泛型类的声明上
 * 通配符不能用在创建对象时的右边创建集合对象上
 *      List<?> list2 = new ArrayList<?>();     // 编译时错误
 *
 * List<?>是List<String>、List<Integer>等各种泛型List的父类
 * 读取List<?>的对象list中的元素时，永远是安全的，因为不管list的真实类型是什么，它包含的都是Object
 * 写入list中的元素时，因为我们不知道c的元素类型，不能向其中添加对象
 * 唯一的例外是null，它是所有类型的成员
 * 可以调用get()方法并使用其返回值，返回值是一个未知的类型，但是它总是一个Object
 *
 * 静态方法无法访问类上定义的泛型；如果静态方法操作的引用数据类型不确定的时候，必须要将泛型定义在方法上
 * 静态方法的泛型需定义在static修饰符之后返回值类型之前
 * public static <T> T getInstance() {}
 *
 *
 *
 * 泛型方法能使方法独立于类而产生变化，以下是一个基本的指导原则：
 *      无论何时，如果你能做到，你就该尽量使用泛型方法。也就是说，如果使用泛型方法将整个类泛型化，那么就应该使用泛型方法。
 *      另外对于一个static的方法而已，无法访问泛型类的类型参数。所以如果static方法要使用泛型能力，就必须使其成为泛型方法
 *
 *
 * <?>      允许所有泛型的引用调用
 *
 * 通配符指定上限
 *      上限extends：使用时指定的类型必须是继承某个类，或者实现某个接口，即<=
 *      <? extends A>       （无穷小， A]，只允许泛型为A及A子类的引用调用
 *
 * 通配符指定下限
 *      下限super：使用时指定的类型不能小于操作的类，即>=
 *      <? super A>         [A，无穷大），只允许泛型为A及A父类的引用调用
 *
 * <? extends Comparable>
 *      只允许泛型为实现 Comparable接口的实现类的引用调用
 *
 * 泛型上下边界
 * 在使用泛型的时候，还可以为传入的泛型类型实参进行上下边界的限制，
 * 如：类型实参只准传入某种类型的父类或某种类型的子类
 *
 * 为泛型添加上边界，即传入的类型实参必须是指定类型的子类型
 * public void showKeyValue1(<? extends Number> obj){
 *     Log.d("泛型测试","key value is " + obj.getKey());
 * }
 *
 * 泛型的上下边界添加，必须与泛型的声明在一起
 *
 *
 *
 * 泛型在继承上的体现
 * 如果B是A的一个子类型（子类或者子接口），而G是具有泛型声明的类或接口，G并不是G的子类型
 * 比如：String是Object的子类，但是List<String>并不是List<Object>的子类
 *
 *
 *
 * 泛型的优点
 *      解决元素存储的安全性问题。编译时就会进行类型检查，保证数据的安全
 *      解决获取数据元素时，需要类型强制转换的问题
 * */
public class GenericsDemo<E> {
    E e;
    public GenericsDemo(E e) {
        this.e = e;
    }

    public static <T> T getInstance() {
        return null;
    }

    /**
     * <T extends Number>
     * 为泛型添加上边界，即传入的类型实参必须是指定类型的子类型
     * */
    public <T extends Number> void showValue(T obj) {
        System.out.println(obj);
        System.out.println(e);
    }

    public static void main(String[] args) {
        List<? extends A> list = new ArrayList<>();     // 把extends看成小于等于，负无穷到B，有上限
                        // <? extends D>    为泛型添加上边界，即传入的类型实参必须是指定类型的子类型
        List<? super A> list1 = new ArrayList<>();      // 把super看成大于等于，有下限没有上限
                        // <? super A>      为泛型添加上边界，即传入的类型实参必须是指定类型的子类型或其本类
//        list.add(new A());      // 编译时错误
        list1.add(new A());
        list1.add(new B());
        list1.add(new C());
        for (Object o : list1) {
            A a = (A) o;
            a.show();
        }
        /**
         * 对于list用了通配符之后就不可以加数据了
         * 限制添加数据，除了添加null之外
         * 所有类类对象都是可以加null的
         * */
        List<?> list2 = new ArrayList<>();
//        list2.add(new B());     // 编译时错误
        list2.add(null);    // 是所有类型的成员
        System.out.println(list2);

//        H<C> h = new H<>(new C());    // 编译错误
        H<D> h = new H<>(new D());
    }
}

class A {
    public void show() {
        System.out.println("A");
    }
}
class B extends A {
    public void show() {
        System.out.println("B");
    }
}
class C extends A {
    public void show() {
        System.out.println("C");
    }
}
class D extends B {
    public void show() {
        System.out.println("D");
    }
}
class E<T> {
    T t;
    public E(T t) {
        this.t = t;
    }
    public void show() {
        System.out.println(t);
    }
}
class F<T> extends E<T> {
    public F(T t) {
        super(t);
    }
}
class H<T extends B> extends GenericsDemo<T> {
    public H(T t) {
        super(t);
    }
}